The goal of this notebook is to compare pseudobulk and bulk calculations to determine which pseudobulk calculation should we proceed with for modeling: the log2 of the sum of raw counts (pseudobulk_log_counts) or the DESeq2-normalized sum of raw counts (pseudobulk_deseq). To this end, we’ll explore pseudobulk expression distributions, compare them to bulk, and also explore distributions of expression where there is disagreement between bulk and single-cell.

Setup

renv::load()

library(ggplot2)
theme_set(theme_bw())

Paths

data_dir <- here::here("analysis", "pseudobulk-bulk-prediction", "data")
tpm_dir <- file.path(data_dir, "tpm")
pseudobulk_dir <- file.path(data_dir, "pseudobulk")


tpm_files <- list.files(
  path = tpm_dir,
  full.names = TRUE,
  pattern = "-tpm\\.tsv$"
)
tpm_names <- stringr::str_split_i(basename(tpm_files), pattern = "-", i = 1)
names(tpm_files) <- tpm_names


pseudobulk_files <- list.files(
  path = pseudobulk_dir,
  full.names = TRUE,
  pattern = "-pseudobulk\\.tsv$"
)
pseudobulk_names <- stringr::str_split_i(basename(pseudobulk_files), pattern = "-", i = 1)
names(pseudobulk_files) <- pseudobulk_names

# Make sure we have the same projects, in the same order
stopifnot(
  all.equal(names(tpm_files), names(pseudobulk_files))
)

Read and prepare input data

We’ll make both a long and wide version of the data for convenience throughout the notebook.

project_long_df_list <- purrr::map2(
  tpm_files, 
  pseudobulk_files, 
  \(tpm_file, pseudo_file) {
    
    dplyr::bind_rows(
      # TPM needs to be in log2 space
      readr::read_tsv(tpm_file, show_col_types = FALSE) |>
        dplyr::mutate(expression = log2(expression)), 
      readr::read_tsv(pseudo_file, show_col_types = FALSE)
    )
  }
)

# Make a wide version as well
project_wide_df_list <- project_long_df_list |>
  purrr::map(
    \(df) {
      df |>
        tidyr::pivot_wider(names_from = expression_type, values_from = expression)
    }
)

Full distributions

First, we’ll visualize distributions of all quantities:

ggplot(purrr::list_rbind(project_long_df_list, names_to = "project_id")) + 
  aes(x = expression, fill = expression_type) + 
  geom_density(alpha = 0.5) + 
  scale_fill_brewer(palette = "Dark2") + 
  facet_grid(
    rows = vars(expression_type), 
    cols = vars(project_id),
    scales = "free_y"
  ) +
  theme(legend.position = "none")

We see big spikes at zero for pseudobulk, not surprisingly. Due to the different transformation approaches, the pseudobulk_deseq version has some negatives for fractional values, but the other quantities have a lower bound of zero. All around, distributions range from their lower bound up to around 20, so it’s nice to know pseudobulk and bulk are definitely on the same scale.

Relationship between quantities

This section will look at the relationship among quantities.

Scatterplots

First, we’ll look at some scatterplots:

  • How similar are the pseudobulk measures themselves?
  • How does each pseudobulk measure compare to bulk?
    • For these plots, we’ll bin data to see the concentration of overlapping points more easily.

In all plots, the red line is y=x, and the blue line is the regression line.

Compare pseudobulk measures

project_wide_df_list |>
  purrr::imap(
    \(df, project_id) {
      
      ggplot(df) + 
        aes(x = pseudobulk_deseq, y = pseudobulk_log_counts) + 
        geom_point(alpha = 0.2, size = 0.5) + 
        geom_smooth(method = "lm", linewidth = 0.5) +
        geom_abline(linewidth = 0.5, color = "red") + 
        facet_wrap(vars(sample_id), nrow = 5) +
        ggtitle(project_id) 

    }
  ) |>
  patchwork::wrap_plots(ncol = 1)

These quantities are exceptionally similar with these differences:

  • Driven by different normalization approaches, genes with very low to zero expression
  • In a handful of samples (1-2 per project), pseudobulk_log_counts appears to have a higher proportion of low to zero counts, and throughout has lower values than pseudobulk_deseq.

Compare pseudobulk to bulk

To make sure we get a good view, we’ll first make the plots and then show them per project with their plot settings.

# Helper function to visualize scatterplots with geom_bin_2d()
make_binned_scatterplots <- function(df, project_id, nbins, facet_rows) {
  p1 <- ggplot(df) + 
    aes(x = pseudobulk_deseq, y = bulk_tpm) + 
    geom_bin_2d(bins = nbins) + 
    geom_smooth(method = "lm", alpha = 0.8, linewidth = 0.5) +
    geom_abline(alpha = 0.8, linewidth = 0.5, color = "red") + 
    facet_wrap(vars(sample_id), nrow = facet_rows) +
    ggtitle("bulk_tpm ~ deseq") 
    
  p2 <- ggplot(df) + 
    aes(x = pseudobulk_log_counts, y = bulk_tpm) + 
    geom_bin_2d(bins = nbins) + 
    geom_smooth(method = "lm", alpha = 0.8, linewidth = 0.5) +
    geom_abline(alpha = 0.8, linewidth = 0.5, color = "red") + 
    facet_wrap(vars(sample_id), nrow = facet_rows) +
    ggtitle("bulk_tpm ~ log_counts") 
      
  print(
    patchwork::wrap_plots(p1, p2, ncol = 2) + patchwork::plot_annotation(title = project_id)
  )
}
make_binned_scatterplots(
  project_wide_df_list$SCPCP000001, 
  project_id = "SCPCP000001",
  nbins = 40,
  facet_rows = 6
)

make_binned_scatterplots(
  project_wide_df_list$SCPCP000002, 
  project_id = "SCPCP000002",
  nbins = 30,
  facet_rows = 6
)

make_binned_scatterplots(
  project_wide_df_list$SCPCP000006, 
  project_id = "SCPCP000006",
  nbins = 50,
  facet_rows = 9
)

make_binned_scatterplots(
  project_wide_df_list$SCPCP000009, 
  project_id = "SCPCP000009",
  nbins = 15,
  facet_rows = 1
)

make_binned_scatterplots(
  project_wide_df_list$SCPCP000017, 
  project_id = "SCPCP000017",
  nbins = 40,
  facet_rows = 7
)

We see high concentrations of points around (0,0) as well as towards the middle values across plots. Next, we’ll look at regression stats for these plots directly.

Statistics: pseudobulk to bulk comparison

Let’s now get some stats for the comparison between bulk and pseudobulk. We’ll fit a linear model for each sample, and display some quantities below both as boxplots and the full table.

# Helper function to plot model statistics from data frame
plot_stats <- function(df, column, title) {
  ggplot(df) + 
    aes(x = expression_type, y = {{column}}, color = expression_type) + 
    geom_boxplot() + 
    scale_color_brewer(palette = "Dark2") +
    ggtitle(title) +
    facet_wrap(vars(project_id), nrow = 1) +
    theme(legend.position = "none")
}

model_samples <- function(id, df) {
  sample_df <- df |>
    dplyr::filter(sample_id == id) 
  
  df_deseq <- sample_df |>
    dplyr::filter(is.finite(pseudobulk_deseq), is.finite(bulk_tpm))
  fit_deseq <- summary(lm(bulk_tpm ~ pseudobulk_deseq, data = df_deseq))

  df_log_counts <- sample_df |>
      dplyr::filter(is.finite(pseudobulk_log_counts), is.finite(bulk_tpm))
  fit_log_counts <- summary(lm(bulk_tpm ~ pseudobulk_log_counts, data = df_log_counts))
      
  # Tabulate and return some fit stats
  data.frame(
    expression_type = c("deseq", "log_counts"),
    rsquared = c(fit_deseq$r.squared, fit_log_counts$r.squared), 
    coeff = c(fit_deseq$coefficients[2], fit_log_counts$coefficients[2]), 
    residual_sd = c(fit_deseq$sigma,  fit_log_counts$sigma)
  )
}

stats_df <- project_wide_df_list |>
  purrr::map(
    \(df) {
      
      # We need to map over sample ids now
      samples <- unique(df$sample_id)
      names(samples) <- samples
      
      fit_table <- samples |>
        purrr::map(model_samples, df) |>
        purrr::list_rbind(names_to = "sample_id")
      
      return(fit_table)

    }
  ) |>
  # now, combine all projects into a single table
  purrr::list_rbind(names_to = "project_id")

patchwork::wrap_plots(
  plot_stats(stats_df, rsquared, "rsquared"),
  plot_stats(stats_df, coeff, "coeff"), 
  plot_stats(stats_df, residual_sd, "residual_sd"), 
  nrow = 3
) 

  • Pseudobulk quantities are exceptionally similar here, which isn’t necessarily surprising given the similarity of the pseudobulk measures
  • Relationships are strongest forSCPCP000001 and SCPCP000002, then SCPCP000006, then SCPCP000009, and finally SCPCP000017 whose relationship is weak if at all present.
  • Samples within a given project have broadly similar coefficients, with a few outliers, suggesting less of an interaction among samples/expression than one might have thought. SCPCP000017 does have more variation here, but also the relationship is very weak in the first place so these different coefficients are not necessarily statistically significantly different.

All the actual values are here:

stats_df

Disagreeing expression

Next, we’ll take a quick look at cases where one modality has zero expression and the other doesn’t. In these cases, if expression is generally high, we have evidence of disagreement/discrepancy between bulk and single-cell that may be interesting to investigate. In this notebook, we’ll just a sense of how much “there is there,” and we’ll leave the in-depth look into any such genes for a subsequent notebook.

In this section, we’ll also use a threshold of 1e-12 for zero here.

Bulk TPM when single-cell is zero

project_wide_df_list |>
  purrr::map(
    \(df) {
      
      low_deseq <- df |>
        dplyr::filter(pseudobulk_deseq <= 1e-12, 
                      bulk_tpm > 1e-12)
      low_logcounts <- df |>
        dplyr::filter(pseudobulk_log_counts <= 1e-12, 
                      bulk_tpm > 1e-12)  

      
      p1 <- ggplot(low_deseq) + 
        aes(x = sample_id, y = bulk_tpm) + 
        ggforce::geom_sina(size = 0.5) +
        theme(axis.text.x = element_blank()) +
        ggtitle("TPM for zero & negative pseudobulk_deseq")
  
      p2 <- ggplot(low_logcounts) + 
        aes(x = sample_id, y = bulk_tpm) + 
        ggforce::geom_sina(size = 0.5) +
        theme(axis.text.x = element_blank()) +
        ggtitle("TPM for zero pseudobulk_log_counts")
      
      patchwork::wrap_plots(p1, p2, nrow = 1)
    }
  ) |>
  patchwork::wrap_plots(ncol = 1)

Single-cell when bulk TPM is zero

project_wide_df_list |>
  purrr::imap(
    \(df, project_id) {
      
      low_bulk <- df |>
        # Even though we don't have 1:1 correspondence between pseudobulks here,
        #  we'll just consider only points where neither is 0 to get a sense.
        dplyr::filter(bulk_tpm <= 1e-12, 
                      pseudobulk_deseq> 1e-12, 
                      pseudobulk_log_counts> 1e-12) |>
        tidyr::pivot_longer(
          contains("pseudobulk"), 
          names_to = "expression_type", 
          values_to = "expression"
        )
      
      ggplot(low_bulk) + 
        aes(x = sample_id, y = expression, color = expression_type) + 
        ggforce::geom_sina(size = 0.5) +
        theme(axis.text.x = element_blank()) +
        ggtitle(project_id)

    }
  ) |>
  patchwork::wrap_plots(ncol = 1, guides = "collect")

From both comparisons, there are a fair number of genes with high expression in one modality and essentially zero in the other. A more careful investigation here look into what exactly these genes are, and whether they have some biological relationship that might suggest modalities are picking up different information.

Do pseudobulks have strong disagreements?

This will show genes strongly affected by different preparation/normalization approaches.

project_wide_df_list |>
  purrr::imap(
    \(df, project_id) {
      
      different_low_pseudo <- df |>
        dplyr::filter((pseudobulk_deseq> 1e-12 & pseudobulk_log_counts <= 1e-12) |
                       (pseudobulk_deseq <=1e-12 & pseudobulk_log_counts > 1e-12) ) |>
        tidyr::pivot_longer(
          contains("pseudobulk"), 
          names_to = "expression_type", 
          values_to = "expression"
        )
      
      ggplot(different_low_pseudo) + 
        aes(x = sample_id, y = expression, color = expression_type) + 
        ggforce::geom_sina(size = 0.5) +
        theme(axis.text.x = element_blank()) +
        ggtitle(project_id)

    }
  ) |>
  patchwork::wrap_plots(ncol = 1, guides = "collect")

Most values seem to be [-2.5, 2.5] for both pseudobulks, but some are getting as high as 6-9.

Session info

sessionInfo()
R version 4.4.0 (2024-04-24)
Platform: aarch64-apple-darwin20
Running under: macOS 15.3

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: America/New_York
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] ggplot2_3.5.1

loaded via a namespace (and not attached):
 [1] sass_0.4.9          generics_0.1.3      tidyr_1.3.1        
 [4] renv_1.0.11         stringi_1.8.4       lattice_0.22-6     
 [7] hms_1.1.3           digest_0.6.37       magrittr_2.0.3     
[10] evaluate_1.0.1      grid_4.4.0          RColorBrewer_1.1-3 
[13] fastmap_1.2.0       Matrix_1.7-1        rprojroot_2.0.4    
[16] jsonlite_1.8.9      BiocManager_1.30.25 mgcv_1.9-1         
[19] purrr_1.0.2         scales_1.3.0        tweenr_2.0.3       
[22] jquerylib_0.1.4     cli_3.6.3           rlang_1.1.4        
[25] crayon_1.5.3        polyclip_1.10-7     bit64_4.5.2        
[28] munsell_0.5.1       splines_4.4.0       withr_3.0.2        
[31] cachem_1.1.0        yaml_2.3.10         tools_4.4.0        
[34] parallel_4.4.0      tzdb_0.4.0          dplyr_1.1.4        
[37] colorspace_2.1-1    here_1.0.1          vctrs_0.6.5        
[40] R6_2.5.1            lifecycle_1.0.4     stringr_1.5.1      
[43] bit_4.5.0.1         MASS_7.3-64         vroom_1.6.5        
[46] pkgconfig_2.0.3     pillar_1.10.0       bslib_0.8.0        
[49] gtable_0.3.6        Rcpp_1.0.13-1       glue_1.8.0         
[52] ggforce_0.4.2       xfun_0.49           tibble_3.2.1       
[55] tidyselect_1.2.1    knitr_1.49          farver_2.1.2       
[58] htmltools_0.5.8.1   nlme_3.1-166        patchwork_1.3.0    
[61] rmarkdown_2.29      labeling_0.4.3      readr_2.1.5        
[64] compiler_4.4.0     
LS0tCnRpdGxlOiAiSW5pdGlhbCBleHBsb3JhdGlvbiBhbmQgY29tcGFyaXNvbiBvZiBwc2V1ZG9idWxrIHZzLiBidWxrIGV4cHJlc3Npb24iCmF1dGhvcjogU3RlcGhhbmllIEouIFNwaWVsbWFuCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgY29kZV9mb2xkaW5nOiBoaWRlCi0tLQoKVGhlIGdvYWwgb2YgdGhpcyBub3RlYm9vayBpcyB0byBjb21wYXJlIHBzZXVkb2J1bGsgYW5kIGJ1bGsgY2FsY3VsYXRpb25zIHRvIGRldGVybWluZSB3aGljaCBwc2V1ZG9idWxrIGNhbGN1bGF0aW9uIHNob3VsZCB3ZSBwcm9jZWVkIHdpdGggZm9yIG1vZGVsaW5nOiB0aGUgbG9nMiBvZiB0aGUgc3VtIG9mIHJhdyBjb3VudHMgKGBwc2V1ZG9idWxrX2xvZ19jb3VudHNgKSBvciB0aGUgYERFU2VxMmAtbm9ybWFsaXplZCBzdW0gb2YgcmF3IGNvdW50cyAoYHBzZXVkb2J1bGtfZGVzZXFgKS4KVG8gdGhpcyBlbmQsIHdlJ2xsIGV4cGxvcmUgcHNldWRvYnVsayBleHByZXNzaW9uIGRpc3RyaWJ1dGlvbnMsIGNvbXBhcmUgdGhlbSB0byBidWxrLCBhbmQgYWxzbyBleHBsb3JlIGRpc3RyaWJ1dGlvbnMgb2YgZXhwcmVzc2lvbiB3aGVyZSB0aGVyZSBpcyBkaXNhZ3JlZW1lbnQgYmV0d2VlbiBidWxrIGFuZCBzaW5nbGUtY2VsbC4KCgojIyBTZXR1cAoKYGBge3Igc2V0dXB9CnJlbnY6OmxvYWQoKQoKbGlicmFyeShnZ3Bsb3QyKQp0aGVtZV9zZXQodGhlbWVfYncoKSkKYGBgCgojIyMgUGF0aHMKCmBgYHtyIHBhdGhzfQpkYXRhX2RpciA8LSBoZXJlOjpoZXJlKCJhbmFseXNpcyIsICJwc2V1ZG9idWxrLWJ1bGstcHJlZGljdGlvbiIsICJkYXRhIikKdHBtX2RpciA8LSBmaWxlLnBhdGgoZGF0YV9kaXIsICJ0cG0iKQpwc2V1ZG9idWxrX2RpciA8LSBmaWxlLnBhdGgoZGF0YV9kaXIsICJwc2V1ZG9idWxrIikKCgp0cG1fZmlsZXMgPC0gbGlzdC5maWxlcygKICBwYXRoID0gdHBtX2RpciwKICBmdWxsLm5hbWVzID0gVFJVRSwKICBwYXR0ZXJuID0gIi10cG1cXC50c3YkIgopCnRwbV9uYW1lcyA8LSBzdHJpbmdyOjpzdHJfc3BsaXRfaShiYXNlbmFtZSh0cG1fZmlsZXMpLCBwYXR0ZXJuID0gIi0iLCBpID0gMSkKbmFtZXModHBtX2ZpbGVzKSA8LSB0cG1fbmFtZXMKCgpwc2V1ZG9idWxrX2ZpbGVzIDwtIGxpc3QuZmlsZXMoCiAgcGF0aCA9IHBzZXVkb2J1bGtfZGlyLAogIGZ1bGwubmFtZXMgPSBUUlVFLAogIHBhdHRlcm4gPSAiLXBzZXVkb2J1bGtcXC50c3YkIgopCnBzZXVkb2J1bGtfbmFtZXMgPC0gc3RyaW5ncjo6c3RyX3NwbGl0X2koYmFzZW5hbWUocHNldWRvYnVsa19maWxlcyksIHBhdHRlcm4gPSAiLSIsIGkgPSAxKQpuYW1lcyhwc2V1ZG9idWxrX2ZpbGVzKSA8LSBwc2V1ZG9idWxrX25hbWVzCgojIE1ha2Ugc3VyZSB3ZSBoYXZlIHRoZSBzYW1lIHByb2plY3RzLCBpbiB0aGUgc2FtZSBvcmRlcgpzdG9waWZub3QoCiAgYWxsLmVxdWFsKG5hbWVzKHRwbV9maWxlcyksIG5hbWVzKHBzZXVkb2J1bGtfZmlsZXMpKQopCmBgYAoKIyMjIFJlYWQgYW5kIHByZXBhcmUgaW5wdXQgZGF0YQoKV2UnbGwgbWFrZSBib3RoIGEgbG9uZyBhbmQgd2lkZSB2ZXJzaW9uIG9mIHRoZSBkYXRhIGZvciBjb252ZW5pZW5jZSB0aHJvdWdob3V0IHRoZSBub3RlYm9vay4KCgpgYGB7cn0KcHJvamVjdF9sb25nX2RmX2xpc3QgPC0gcHVycnI6Om1hcDIoCiAgdHBtX2ZpbGVzLCAKICBwc2V1ZG9idWxrX2ZpbGVzLCAKICBcKHRwbV9maWxlLCBwc2V1ZG9fZmlsZSkgewogICAgCiAgICBkcGx5cjo6YmluZF9yb3dzKAogICAgICAjIFRQTSBuZWVkcyB0byBiZSBpbiBsb2cyIHNwYWNlCiAgICAgIHJlYWRyOjpyZWFkX3Rzdih0cG1fZmlsZSwgc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkgfD4KICAgICAgICBkcGx5cjo6bXV0YXRlKGV4cHJlc3Npb24gPSBsb2cyKGV4cHJlc3Npb24pKSwgCiAgICAgIHJlYWRyOjpyZWFkX3Rzdihwc2V1ZG9fZmlsZSwgc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkKICAgICkKICB9CikKCiMgTWFrZSBhIHdpZGUgdmVyc2lvbiBhcyB3ZWxsCnByb2plY3Rfd2lkZV9kZl9saXN0IDwtIHByb2plY3RfbG9uZ19kZl9saXN0IHw+CiAgcHVycnI6Om1hcCgKICAgIFwoZGYpIHsKICAgICAgZGYgfD4KICAgICAgICB0aWR5cjo6cGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IGV4cHJlc3Npb25fdHlwZSwgdmFsdWVzX2Zyb20gPSBleHByZXNzaW9uKQogICAgfQopCmBgYAoKCiMjIEZ1bGwgZGlzdHJpYnV0aW9ucwoKRmlyc3QsIHdlJ2xsIHZpc3VhbGl6ZSBkaXN0cmlidXRpb25zIG9mIGFsbCBxdWFudGl0aWVzOgoKYGBge3IsIGZpZy53aWR0aCA9IDcsIGZpZy5oZWlnaHQgPSA1LCB3YXJuaW5nID0gRkFMU0V9CmdncGxvdChwdXJycjo6bGlzdF9yYmluZChwcm9qZWN0X2xvbmdfZGZfbGlzdCwgbmFtZXNfdG8gPSAicHJvamVjdF9pZCIpKSArIAogIGFlcyh4ID0gZXhwcmVzc2lvbiwgZmlsbCA9IGV4cHJlc3Npb25fdHlwZSkgKyAKICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjUpICsgCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIpICsgCiAgZmFjZXRfZ3JpZCgKICAgIHJvd3MgPSB2YXJzKGV4cHJlc3Npb25fdHlwZSksIAogICAgY29scyA9IHZhcnMocHJvamVjdF9pZCksCiAgICBzY2FsZXMgPSAiZnJlZV95IgogICkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgpXZSBzZWUgYmlnIHNwaWtlcyBhdCB6ZXJvIGZvciBwc2V1ZG9idWxrLCBub3Qgc3VycHJpc2luZ2x5LgpEdWUgdG8gdGhlIGRpZmZlcmVudCB0cmFuc2Zvcm1hdGlvbiBhcHByb2FjaGVzLCB0aGUgYHBzZXVkb2J1bGtfZGVzZXFgIHZlcnNpb24gaGFzIHNvbWUgbmVnYXRpdmVzIGZvciBmcmFjdGlvbmFsIHZhbHVlcywgYnV0IHRoZSBvdGhlciBxdWFudGl0aWVzIGhhdmUgYSBsb3dlciBib3VuZCBvZiB6ZXJvLgpBbGwgYXJvdW5kLCBkaXN0cmlidXRpb25zIHJhbmdlIGZyb20gdGhlaXIgbG93ZXIgYm91bmQgdXAgdG8gYXJvdW5kIDIwLCBzbyBpdCdzIG5pY2UgdG8ga25vdyBwc2V1ZG9idWxrIGFuZCBidWxrIGFyZSBkZWZpbml0ZWx5IG9uIHRoZSBzYW1lIHNjYWxlLgoKICAKIyMgUmVsYXRpb25zaGlwIGJldHdlZW4gcXVhbnRpdGllcwoKVGhpcyBzZWN0aW9uIHdpbGwgbG9vayBhdCB0aGUgcmVsYXRpb25zaGlwIGFtb25nIHF1YW50aXRpZXMuCgogIAojIyMgU2NhdHRlcnBsb3RzIAoKRmlyc3QsIHdlJ2xsIGxvb2sgYXQgc29tZSBzY2F0dGVycGxvdHM6CgoqIEhvdyBzaW1pbGFyIGFyZSB0aGUgcHNldWRvYnVsayBtZWFzdXJlcyB0aGVtc2VsdmVzPwoqIEhvdyBkb2VzIGVhY2ggcHNldWRvYnVsayBtZWFzdXJlIGNvbXBhcmUgdG8gYnVsaz8KICAqIEZvciB0aGVzZSBwbG90cywgd2UnbGwgYmluIGRhdGEgdG8gc2VlIHRoZSBjb25jZW50cmF0aW9uIG9mIG92ZXJsYXBwaW5nIHBvaW50cyBtb3JlIGVhc2lseS4KCkluIGFsbCBwbG90cywgdGhlIHJlZCBsaW5lIGlzIGB5PXhgLCBhbmQgdGhlIGJsdWUgbGluZSBpcyB0aGUgcmVncmVzc2lvbiBsaW5lLgoKIyMjIyBDb21wYXJlIHBzZXVkb2J1bGsgbWVhc3VyZXMKCmBgYHtyIGZpZy5oZWlnaHQ9MzQsIGZpZy53aWR0aD04LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpwcm9qZWN0X3dpZGVfZGZfbGlzdCB8PgogIHB1cnJyOjppbWFwKAogICAgXChkZiwgcHJvamVjdF9pZCkgewogICAgICAKICAgICAgZ2dwbG90KGRmKSArIAogICAgICAgIGFlcyh4ID0gcHNldWRvYnVsa19kZXNlcSwgeSA9IHBzZXVkb2J1bGtfbG9nX2NvdW50cykgKyAKICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC4yLCBzaXplID0gMC41KSArIAogICAgICAgIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIGxpbmV3aWR0aCA9IDAuNSkgKwogICAgICAgIGdlb21fYWJsaW5lKGxpbmV3aWR0aCA9IDAuNSwgY29sb3IgPSAicmVkIikgKyAKICAgICAgICBmYWNldF93cmFwKHZhcnMoc2FtcGxlX2lkKSwgbnJvdyA9IDUpICsKICAgICAgICBnZ3RpdGxlKHByb2plY3RfaWQpIAoKICAgIH0KICApIHw+CiAgcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKG5jb2wgPSAxKQpgYGAKCgpUaGVzZSBxdWFudGl0aWVzIGFyZSBleGNlcHRpb25hbGx5IHNpbWlsYXIgd2l0aCB0aGVzZSBkaWZmZXJlbmNlczoKCiogRHJpdmVuIGJ5IGRpZmZlcmVudCBub3JtYWxpemF0aW9uIGFwcHJvYWNoZXMsIGdlbmVzIHdpdGggdmVyeSBsb3cgdG8gemVybyBleHByZXNzaW9uCiogSW4gYSBoYW5kZnVsIG9mIHNhbXBsZXMgKDEtMiBwZXIgcHJvamVjdCksIGBwc2V1ZG9idWxrX2xvZ19jb3VudHNgIGFwcGVhcnMgdG8gaGF2ZSBhIGhpZ2hlciBwcm9wb3J0aW9uIG9mIGxvdyB0byB6ZXJvIGNvdW50cywgYW5kIHRocm91Z2hvdXQgaGFzIGxvd2VyIHZhbHVlcyB0aGFuIGBwc2V1ZG9idWxrX2Rlc2VxYC4KCgojIyMjIENvbXBhcmUgcHNldWRvYnVsayB0byBidWxrCgpUbyBtYWtlIHN1cmUgd2UgZ2V0IGEgZ29vZCB2aWV3LCB3ZSdsbCBmaXJzdCBtYWtlIHRoZSBwbG90cyBhbmQgdGhlbiBzaG93IHRoZW0gcGVyIHByb2plY3Qgd2l0aCB0aGVpciBwbG90IHNldHRpbmdzLgoKYGBge3J9CiMgSGVscGVyIGZ1bmN0aW9uIHRvIHZpc3VhbGl6ZSBzY2F0dGVycGxvdHMgd2l0aCBnZW9tX2Jpbl8yZCgpCm1ha2VfYmlubmVkX3NjYXR0ZXJwbG90cyA8LSBmdW5jdGlvbihkZiwgcHJvamVjdF9pZCwgbmJpbnMsIGZhY2V0X3Jvd3MpIHsKICBwMSA8LSBnZ3Bsb3QoZGYpICsgCiAgICBhZXMoeCA9IHBzZXVkb2J1bGtfZGVzZXEsIHkgPSBidWxrX3RwbSkgKyAKICAgIGdlb21fYmluXzJkKGJpbnMgPSBuYmlucykgKyAKICAgIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIGFscGhhID0gMC44LCBsaW5ld2lkdGggPSAwLjUpICsKICAgIGdlb21fYWJsaW5lKGFscGhhID0gMC44LCBsaW5ld2lkdGggPSAwLjUsIGNvbG9yID0gInJlZCIpICsgCiAgICBmYWNldF93cmFwKHZhcnMoc2FtcGxlX2lkKSwgbnJvdyA9IGZhY2V0X3Jvd3MpICsKICAgIGdndGl0bGUoImJ1bGtfdHBtIH4gZGVzZXEiKSAKICAgIAogIHAyIDwtIGdncGxvdChkZikgKyAKICAgIGFlcyh4ID0gcHNldWRvYnVsa19sb2dfY291bnRzLCB5ID0gYnVsa190cG0pICsgCiAgICBnZW9tX2Jpbl8yZChiaW5zID0gbmJpbnMpICsgCiAgICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBhbHBoYSA9IDAuOCwgbGluZXdpZHRoID0gMC41KSArCiAgICBnZW9tX2FibGluZShhbHBoYSA9IDAuOCwgbGluZXdpZHRoID0gMC41LCBjb2xvciA9ICJyZWQiKSArIAogICAgZmFjZXRfd3JhcCh2YXJzKHNhbXBsZV9pZCksIG5yb3cgPSBmYWNldF9yb3dzKSArCiAgICBnZ3RpdGxlKCJidWxrX3RwbSB+IGxvZ19jb3VudHMiKSAKICAgICAgCiAgcHJpbnQoCiAgICBwYXRjaHdvcms6OndyYXBfcGxvdHMocDEsIHAyLCBuY29sID0gMikgKyBwYXRjaHdvcms6OnBsb3RfYW5ub3RhdGlvbih0aXRsZSA9IHByb2plY3RfaWQpCiAgKQp9CmBgYAoKCmBgYHtyIGZpZy5oZWlnaHQgPSAxMiwgZmlnLndpZHRoID0gMTIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm1ha2VfYmlubmVkX3NjYXR0ZXJwbG90cygKICBwcm9qZWN0X3dpZGVfZGZfbGlzdCRTQ1BDUDAwMDAwMSwgCiAgcHJvamVjdF9pZCA9ICJTQ1BDUDAwMDAwMSIsCiAgbmJpbnMgPSA0MCwKICBmYWNldF9yb3dzID0gNgopCmBgYAoKCgpgYGB7ciBmaWcuaGVpZ2h0ID0gMTAsIGZpZy53aWR0aCA9IDEyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQptYWtlX2Jpbm5lZF9zY2F0dGVycGxvdHMoCiAgcHJvamVjdF93aWRlX2RmX2xpc3QkU0NQQ1AwMDAwMDIsIAogIHByb2plY3RfaWQgPSAiU0NQQ1AwMDAwMDIiLAogIG5iaW5zID0gMzAsCiAgZmFjZXRfcm93cyA9IDYKKQpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0ID0gMTIsIGZpZy53aWR0aCA9IDEyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQptYWtlX2Jpbm5lZF9zY2F0dGVycGxvdHMoCiAgcHJvamVjdF93aWRlX2RmX2xpc3QkU0NQQ1AwMDAwMDYsIAogIHByb2plY3RfaWQgPSAiU0NQQ1AwMDAwMDYiLAogIG5iaW5zID0gNTAsCiAgZmFjZXRfcm93cyA9IDkKKQpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0ID0gMywgZmlnLndpZHRoID0gMTIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm1ha2VfYmlubmVkX3NjYXR0ZXJwbG90cygKICBwcm9qZWN0X3dpZGVfZGZfbGlzdCRTQ1BDUDAwMDAwOSwgCiAgcHJvamVjdF9pZCA9ICJTQ1BDUDAwMDAwOSIsCiAgbmJpbnMgPSAxNSwKICBmYWNldF9yb3dzID0gMQopCmBgYAoKCmBgYHtyIGZpZy5oZWlnaHQgPSA4LCBmaWcud2lkdGggPSAxMiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbWFrZV9iaW5uZWRfc2NhdHRlcnBsb3RzKAogIHByb2plY3Rfd2lkZV9kZl9saXN0JFNDUENQMDAwMDE3LCAKICBwcm9qZWN0X2lkID0gIlNDUENQMDAwMDE3IiwKICBuYmlucyA9IDQwLAogIGZhY2V0X3Jvd3MgPSA3CikKYGBgCgpXZSBzZWUgaGlnaCBjb25jZW50cmF0aW9ucyBvZiBwb2ludHMgYXJvdW5kIGAoMCwwYCkgYXMgd2VsbCBhcyB0b3dhcmRzIHRoZSBtaWRkbGUgdmFsdWVzIGFjcm9zcyBwbG90cy4KTmV4dCwgd2UnbGwgbG9vayBhdCByZWdyZXNzaW9uIHN0YXRzIGZvciB0aGVzZSBwbG90cyBkaXJlY3RseS4KCgojIyMgU3RhdGlzdGljczogcHNldWRvYnVsayB0byBidWxrIGNvbXBhcmlzb24KCkxldCdzIG5vdyBnZXQgc29tZSBzdGF0cyBmb3IgdGhlIGNvbXBhcmlzb24gYmV0d2VlbiBidWxrIGFuZCBwc2V1ZG9idWxrLgpXZSdsbCBmaXQgYSBsaW5lYXIgbW9kZWwgZm9yIGVhY2ggc2FtcGxlLCBhbmQgZGlzcGxheSBzb21lIHF1YW50aXRpZXMgYmVsb3cgYm90aCBhcyBib3hwbG90cyBhbmQgdGhlIGZ1bGwgdGFibGUuCgpgYGB7ciBmaWcuaGVpZ2h0ID0gMTAsIGZpZy53aWR0aCA9IDEyfQojIEhlbHBlciBmdW5jdGlvbiB0byBwbG90IG1vZGVsIHN0YXRpc3RpY3MgZnJvbSBkYXRhIGZyYW1lCnBsb3Rfc3RhdHMgPC0gZnVuY3Rpb24oZGYsIGNvbHVtbiwgdGl0bGUpIHsKICBnZ3Bsb3QoZGYpICsgCiAgICBhZXMoeCA9IGV4cHJlc3Npb25fdHlwZSwgeSA9IHt7Y29sdW1ufX0sIGNvbG9yID0gZXhwcmVzc2lvbl90eXBlKSArIAogICAgZ2VvbV9ib3hwbG90KCkgKyAKICAgIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIkRhcmsyIikgKwogICAgZ2d0aXRsZSh0aXRsZSkgKwogICAgZmFjZXRfd3JhcCh2YXJzKHByb2plY3RfaWQpLCBucm93ID0gMSkgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQp9Cgptb2RlbF9zYW1wbGVzIDwtIGZ1bmN0aW9uKGlkLCBkZikgewogIHNhbXBsZV9kZiA8LSBkZiB8PgogICAgZHBseXI6OmZpbHRlcihzYW1wbGVfaWQgPT0gaWQpIAogIAogIGRmX2Rlc2VxIDwtIHNhbXBsZV9kZiB8PgogICAgZHBseXI6OmZpbHRlcihpcy5maW5pdGUocHNldWRvYnVsa19kZXNlcSksIGlzLmZpbml0ZShidWxrX3RwbSkpCiAgZml0X2Rlc2VxIDwtIHN1bW1hcnkobG0oYnVsa190cG0gfiBwc2V1ZG9idWxrX2Rlc2VxLCBkYXRhID0gZGZfZGVzZXEpKQoKICBkZl9sb2dfY291bnRzIDwtIHNhbXBsZV9kZiB8PgogICAgICBkcGx5cjo6ZmlsdGVyKGlzLmZpbml0ZShwc2V1ZG9idWxrX2xvZ19jb3VudHMpLCBpcy5maW5pdGUoYnVsa190cG0pKQogIGZpdF9sb2dfY291bnRzIDwtIHN1bW1hcnkobG0oYnVsa190cG0gfiBwc2V1ZG9idWxrX2xvZ19jb3VudHMsIGRhdGEgPSBkZl9sb2dfY291bnRzKSkKICAgICAgCiAgIyBUYWJ1bGF0ZSBhbmQgcmV0dXJuIHNvbWUgZml0IHN0YXRzCiAgZGF0YS5mcmFtZSgKICAgIGV4cHJlc3Npb25fdHlwZSA9IGMoImRlc2VxIiwgImxvZ19jb3VudHMiKSwKICAgIHJzcXVhcmVkID0gYyhmaXRfZGVzZXEkci5zcXVhcmVkLCBmaXRfbG9nX2NvdW50cyRyLnNxdWFyZWQpLCAKICAgIGNvZWZmID0gYyhmaXRfZGVzZXEkY29lZmZpY2llbnRzWzJdLCBmaXRfbG9nX2NvdW50cyRjb2VmZmljaWVudHNbMl0pLCAKICAgIHJlc2lkdWFsX3NkID0gYyhmaXRfZGVzZXEkc2lnbWEsICBmaXRfbG9nX2NvdW50cyRzaWdtYSkKICApCn0KCnN0YXRzX2RmIDwtIHByb2plY3Rfd2lkZV9kZl9saXN0IHw+CiAgcHVycnI6Om1hcCgKICAgIFwoZGYpIHsKICAgICAgCiAgICAgICMgV2UgbmVlZCB0byBtYXAgb3ZlciBzYW1wbGUgaWRzIG5vdwogICAgICBzYW1wbGVzIDwtIHVuaXF1ZShkZiRzYW1wbGVfaWQpCiAgICAgIG5hbWVzKHNhbXBsZXMpIDwtIHNhbXBsZXMKICAgICAgCiAgICAgIGZpdF90YWJsZSA8LSBzYW1wbGVzIHw+CiAgICAgICAgcHVycnI6Om1hcChtb2RlbF9zYW1wbGVzLCBkZikgfD4KICAgICAgICBwdXJycjo6bGlzdF9yYmluZChuYW1lc190byA9ICJzYW1wbGVfaWQiKQogICAgICAKICAgICAgcmV0dXJuKGZpdF90YWJsZSkKCiAgICB9CiAgKSB8PgogICMgbm93LCBjb21iaW5lIGFsbCBwcm9qZWN0cyBpbnRvIGEgc2luZ2xlIHRhYmxlCiAgcHVycnI6Omxpc3RfcmJpbmQobmFtZXNfdG8gPSAicHJvamVjdF9pZCIpCgpwYXRjaHdvcms6OndyYXBfcGxvdHMoCiAgcGxvdF9zdGF0cyhzdGF0c19kZiwgcnNxdWFyZWQsICJyc3F1YXJlZCIpLAogIHBsb3Rfc3RhdHMoc3RhdHNfZGYsIGNvZWZmLCAiY29lZmYiKSwgCiAgcGxvdF9zdGF0cyhzdGF0c19kZiwgcmVzaWR1YWxfc2QsICJyZXNpZHVhbF9zZCIpLCAKICBucm93ID0gMwopIApgYGAKCiogUHNldWRvYnVsayBxdWFudGl0aWVzIGFyZSBleGNlcHRpb25hbGx5IHNpbWlsYXIgaGVyZSwgd2hpY2ggaXNuJ3QgbmVjZXNzYXJpbHkgc3VycHJpc2luZyBnaXZlbiB0aGUgc2ltaWxhcml0eSBvZiB0aGUgcHNldWRvYnVsayBtZWFzdXJlcwoqIFJlbGF0aW9uc2hpcHMgYXJlIHN0cm9uZ2VzdCBmb3JgU0NQQ1AwMDAwMDFgIGFuZCBgU0NQQ1AwMDAwMDJgLCB0aGVuIGBTQ1BDUDAwMDAwNmAsIHRoZW4gYFNDUENQMDAwMDA5YCwgYW5kIGZpbmFsbHkgYFNDUENQMDAwMDE3YCB3aG9zZSByZWxhdGlvbnNoaXAgaXMgd2VhayBpZiBhdCBhbGwgcHJlc2VudC4KKiBTYW1wbGVzIHdpdGhpbiBhIGdpdmVuIHByb2plY3QgaGF2ZSBicm9hZGx5IHNpbWlsYXIgY29lZmZpY2llbnRzLCB3aXRoIGEgZmV3IG91dGxpZXJzLCBzdWdnZXN0aW5nIGxlc3Mgb2YgYW4gaW50ZXJhY3Rpb24gYW1vbmcgc2FtcGxlcy9leHByZXNzaW9uIHRoYW4gb25lIG1pZ2h0IGhhdmUgdGhvdWdodC4KYFNDUENQMDAwMDE3YCBkb2VzIGhhdmUgbW9yZSB2YXJpYXRpb24gaGVyZSwgYnV0IGFsc28gdGhlIHJlbGF0aW9uc2hpcCBpcyB2ZXJ5IHdlYWsgaW4gdGhlIGZpcnN0IHBsYWNlIHNvIHRoZXNlIGRpZmZlcmVudCBjb2VmZmljaWVudHMgYXJlIG5vdCBuZWNlc3NhcmlseSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50bHkgZGlmZmVyZW50LgoKQWxsIHRoZSBhY3R1YWwgdmFsdWVzIGFyZSBoZXJlOgoKCmBgYHtyfQpzdGF0c19kZgpgYGAKCgojIyBEaXNhZ3JlZWluZyBleHByZXNzaW9uCgpOZXh0LCB3ZSdsbCB0YWtlIGEgcXVpY2sgbG9vayBhdCBjYXNlcyB3aGVyZSBvbmUgbW9kYWxpdHkgaGFzIHplcm8gZXhwcmVzc2lvbiBhbmQgdGhlIG90aGVyIGRvZXNuJ3QuCkluIHRoZXNlIGNhc2VzLCBpZiBleHByZXNzaW9uIGlzIGdlbmVyYWxseSBoaWdoLCB3ZSBoYXZlIGV2aWRlbmNlIG9mIGRpc2FncmVlbWVudC9kaXNjcmVwYW5jeSBiZXR3ZWVuIGJ1bGsgYW5kIHNpbmdsZS1jZWxsIHRoYXQgbWF5IGJlIGludGVyZXN0aW5nIHRvIGludmVzdGlnYXRlLgpJbiB0aGlzIG5vdGVib29rLCB3ZSdsbCBqdXN0IGEgc2Vuc2Ugb2YgaG93IG11Y2ggInRoZXJlIGlzIHRoZXJlLCIgYW5kIHdlJ2xsIGxlYXZlIHRoZSBpbi1kZXB0aCBsb29rIGludG8gYW55IHN1Y2ggZ2VuZXMgZm9yIGEgc3Vic2VxdWVudCBub3RlYm9vay4KCkluIHRoaXMgc2VjdGlvbiwgd2UnbGwgYWxzbyB1c2UgYSB0aHJlc2hvbGQgb2YgYDFlLTEyYCBmb3IgemVybyBoZXJlLgoKCiMjIyBCdWxrIFRQTSB3aGVuIHNpbmdsZS1jZWxsIGlzIHplcm8KCgpgYGB7ciBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9MTIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnByb2plY3Rfd2lkZV9kZl9saXN0IHw+CiAgcHVycnI6Om1hcCgKICAgIFwoZGYpIHsKICAgICAgCiAgICAgIGxvd19kZXNlcSA8LSBkZiB8PgogICAgICAgIGRwbHlyOjpmaWx0ZXIocHNldWRvYnVsa19kZXNlcSA8PSAxZS0xMiwgCiAgICAgICAgICAgICAgICAgICAgICBidWxrX3RwbSA+IDFlLTEyKQogICAgICBsb3dfbG9nY291bnRzIDwtIGRmIHw+CiAgICAgICAgZHBseXI6OmZpbHRlcihwc2V1ZG9idWxrX2xvZ19jb3VudHMgPD0gMWUtMTIsIAogICAgICAgICAgICAgICAgICAgICAgYnVsa190cG0gPiAxZS0xMikgIAoKICAgICAgCiAgICAgIHAxIDwtIGdncGxvdChsb3dfZGVzZXEpICsgCiAgICAgICAgYWVzKHggPSBzYW1wbGVfaWQsIHkgPSBidWxrX3RwbSkgKyAKICAgICAgICBnZ2ZvcmNlOjpnZW9tX3NpbmEoc2l6ZSA9IDAuNSkgKwogICAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgICAgICAgZ2d0aXRsZSgiVFBNIGZvciB6ZXJvICYgbmVnYXRpdmUgcHNldWRvYnVsa19kZXNlcSIpCiAgCiAgICAgIHAyIDwtIGdncGxvdChsb3dfbG9nY291bnRzKSArIAogICAgICAgIGFlcyh4ID0gc2FtcGxlX2lkLCB5ID0gYnVsa190cG0pICsgCiAgICAgICAgZ2dmb3JjZTo6Z2VvbV9zaW5hKHNpemUgPSAwLjUpICsKICAgICAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgICAgIGdndGl0bGUoIlRQTSBmb3IgemVybyBwc2V1ZG9idWxrX2xvZ19jb3VudHMiKQogICAgICAKICAgICAgcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHAxLCBwMiwgbnJvdyA9IDEpCiAgICB9CiAgKSB8PgogIHBhdGNod29yazo6d3JhcF9wbG90cyhuY29sID0gMSkKYGBgCgoKCiMjIyBTaW5nbGUtY2VsbCB3aGVuIGJ1bGsgVFBNIGlzIHplcm8KCgoKYGBge3IgZmlnLmhlaWdodD0xMiwgZmlnLndpZHRoPTEyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpwcm9qZWN0X3dpZGVfZGZfbGlzdCB8PgogIHB1cnJyOjppbWFwKAogICAgXChkZiwgcHJvamVjdF9pZCkgewogICAgICAKICAgICAgbG93X2J1bGsgPC0gZGYgfD4KICAgICAgICAjIEV2ZW4gdGhvdWdoIHdlIGRvbid0IGhhdmUgMToxIGNvcnJlc3BvbmRlbmNlIGJldHdlZW4gcHNldWRvYnVsa3MgaGVyZSwKICAgICAgICAjICB3ZSdsbCBqdXN0IGNvbnNpZGVyIG9ubHkgcG9pbnRzIHdoZXJlIG5laXRoZXIgaXMgMCB0byBnZXQgYSBzZW5zZS4KICAgICAgICBkcGx5cjo6ZmlsdGVyKGJ1bGtfdHBtIDw9IDFlLTEyLCAKICAgICAgICAgICAgICAgICAgICAgIHBzZXVkb2J1bGtfZGVzZXE+IDFlLTEyLCAKICAgICAgICAgICAgICAgICAgICAgIHBzZXVkb2J1bGtfbG9nX2NvdW50cz4gMWUtMTIpIHw+CiAgICAgICAgdGlkeXI6OnBpdm90X2xvbmdlcigKICAgICAgICAgIGNvbnRhaW5zKCJwc2V1ZG9idWxrIiksIAogICAgICAgICAgbmFtZXNfdG8gPSAiZXhwcmVzc2lvbl90eXBlIiwgCiAgICAgICAgICB2YWx1ZXNfdG8gPSAiZXhwcmVzc2lvbiIKICAgICAgICApCiAgICAgIAogICAgICBnZ3Bsb3QobG93X2J1bGspICsgCiAgICAgICAgYWVzKHggPSBzYW1wbGVfaWQsIHkgPSBleHByZXNzaW9uLCBjb2xvciA9IGV4cHJlc3Npb25fdHlwZSkgKyAKICAgICAgICBnZ2ZvcmNlOjpnZW9tX3NpbmEoc2l6ZSA9IDAuNSkgKwogICAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgICAgICAgZ2d0aXRsZShwcm9qZWN0X2lkKQoKICAgIH0KICApIHw+CiAgcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKG5jb2wgPSAxLCBndWlkZXMgPSAiY29sbGVjdCIpCmBgYAoKCgpGcm9tIGJvdGggY29tcGFyaXNvbnMsIHRoZXJlIGFyZSBhIGZhaXIgbnVtYmVyIG9mIGdlbmVzIHdpdGggaGlnaCBleHByZXNzaW9uIGluIG9uZSBtb2RhbGl0eSBhbmQgZXNzZW50aWFsbHkgemVybyBpbiB0aGUgb3RoZXIuIApBIG1vcmUgY2FyZWZ1bCBpbnZlc3RpZ2F0aW9uIGhlcmUgbG9vayBpbnRvIHdoYXQgZXhhY3RseSB0aGVzZSBnZW5lcyBhcmUsIGFuZCB3aGV0aGVyIHRoZXkgaGF2ZSBzb21lIGJpb2xvZ2ljYWwgcmVsYXRpb25zaGlwIHRoYXQgbWlnaHQgc3VnZ2VzdCBtb2RhbGl0aWVzIGFyZSBwaWNraW5nIHVwIGRpZmZlcmVudCBpbmZvcm1hdGlvbi4KCgojIyMgRG8gcHNldWRvYnVsa3MgaGF2ZSBzdHJvbmcgZGlzYWdyZWVtZW50cz8KClRoaXMgd2lsbCBzaG93IGdlbmVzIHN0cm9uZ2x5IGFmZmVjdGVkIGJ5IGRpZmZlcmVudCBwcmVwYXJhdGlvbi9ub3JtYWxpemF0aW9uIGFwcHJvYWNoZXMuCgoKYGBge3IgZmlnLmhlaWdodD0xMiwgZmlnLndpZHRoPTEyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpwcm9qZWN0X3dpZGVfZGZfbGlzdCB8PgogIHB1cnJyOjppbWFwKAogICAgXChkZiwgcHJvamVjdF9pZCkgewogICAgICAKICAgICAgZGlmZmVyZW50X2xvd19wc2V1ZG8gPC0gZGYgfD4KICAgICAgICBkcGx5cjo6ZmlsdGVyKChwc2V1ZG9idWxrX2Rlc2VxPiAxZS0xMiAmIHBzZXVkb2J1bGtfbG9nX2NvdW50cyA8PSAxZS0xMikgfAogICAgICAgICAgICAgICAgICAgICAgIChwc2V1ZG9idWxrX2Rlc2VxIDw9MWUtMTIgJiBwc2V1ZG9idWxrX2xvZ19jb3VudHMgPiAxZS0xMikgKSB8PgogICAgICAgIHRpZHlyOjpwaXZvdF9sb25nZXIoCiAgICAgICAgICBjb250YWlucygicHNldWRvYnVsayIpLCAKICAgICAgICAgIG5hbWVzX3RvID0gImV4cHJlc3Npb25fdHlwZSIsIAogICAgICAgICAgdmFsdWVzX3RvID0gImV4cHJlc3Npb24iCiAgICAgICAgKQogICAgICAKICAgICAgZ2dwbG90KGRpZmZlcmVudF9sb3dfcHNldWRvKSArIAogICAgICAgIGFlcyh4ID0gc2FtcGxlX2lkLCB5ID0gZXhwcmVzc2lvbiwgY29sb3IgPSBleHByZXNzaW9uX3R5cGUpICsgCiAgICAgICAgZ2dmb3JjZTo6Z2VvbV9zaW5hKHNpemUgPSAwLjUpICsKICAgICAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgICAgIGdndGl0bGUocHJvamVjdF9pZCkKCgogICAgfQogICkgfD4KICBwYXRjaHdvcms6OndyYXBfcGxvdHMobmNvbCA9IDEsIGd1aWRlcyA9ICJjb2xsZWN0IikKYGBgCgpNb3N0IHZhbHVlcyBzZWVtIHRvIGJlIGBbLTIuNSwgMi41XWAgZm9yIGJvdGggcHNldWRvYnVsa3MsIGJ1dCBzb21lIGFyZSBnZXR0aW5nIGFzIGhpZ2ggYXMgNi05LiAKCgojIyBTZXNzaW9uIGluZm8KCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYA==